package proyecto2.logica;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class NFA {

    ArrayList<Estado> estados;
    ArrayList<String> alfabeto;
    ArrayList<Transicion> transiciones;
    Estado inicial;
    ArrayList<Estado> finales;
    ArrayList<ArrayList<listObj>> matDFA;
    ArrayList<ArrayList<String>> matAux;

    public NFA() {
    }

    public NFA(ArrayList<Estado> f) {
        this.finales = f;

    }

    public NFA(ArrayList<Estado> estados, ArrayList<String> alfabeto, ArrayList<Transicion> transiciones, Estado inicial, ArrayList<Estado> finales) {
        this.estados = estados;
        this.alfabeto = alfabeto;
        this.transiciones = transiciones;
        this.inicial = inicial;
        this.finales = finales;
    }

    public ArrayList<Estado> getEstados() {
        return estados;
    }

    public void setEstados(ArrayList<Estado> estados) {
        this.estados = estados;
    }

    public ArrayList<String> getAlfabeto() {
        return alfabeto;
    }

    public void setAlfabeto(ArrayList<String> alfabeto) {
        this.alfabeto = alfabeto;
    }

    public ArrayList<Transicion> getTransiciones() {
        return transiciones;
    }

    public void setTransiciones(ArrayList<Transicion> transiciones) {
        this.transiciones = transiciones;
    }

    public Estado getInicial() {
        return inicial;
    }

    public void setInicial(Estado Inicia) {
        this.inicial = Inicia;
    }

    public ArrayList<Estado> getFinales() {
        return finales;
    }

    public void setFinales(ArrayList<Estado> finales) {
        this.finales = finales;
    }

    public boolean isDFA() {
        for (Estado es : estados) {
            for (String alfa : alfabeto) {
                if (getEstadosTransicion(es, alfa).size() > 1) {
                    return false;
                }
            }
        }
        return true;
    }

    public DFA toDFA() {
        if (isDFA()) {
            return new DFA(estados, alfabeto, transiciones, inicial, finales);
        } else {
            return construyeAutomataDFA();
        }
    }

    public List[][] creaMAtriz() {

        List[][] foo = new ArrayList[estados.size()][alfabeto.size() + 1];

        //System.out.println("--1--");
        for (int i = 0; i < estados.size(); i++) {
            for (int j = 0; j < alfabeto.size(); j++) {

                foo[i][j] = getEstadosTransicion(estados.get(i), alfabeto.get(j));
                //System.out.println(foo[i][j].toString());

            }
            foo[i][alfabeto.size()] = getEstadosTransicion(estados.get(i), "epsilon");
            foo[i][alfabeto.size()].add(estados.get(i));

            //System.out.println(foo[i][alfabeto.size()].toString());
            //System.out.println("--fin--");
        }
        //System.out.println("--2--");

        return foo;
    }

    public void matDFA() {
        int n = 1;
        matDFA = null;
        matDFA = new ArrayList<>();
        matAux = null;
        matAux = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            ArrayList<listObj> listaobjetos = new ArrayList<>();
            matDFA.add(listaobjetos);

            if (i == 0) {
                matAux.add(new ArrayList<String>());
                matAux.get(i).add(estados.get(i).getId());
            }

            for (int j = 0; j < alfabeto.size(); j++) {

                ArrayList<String> listE = getEstadosTransicion(matAux.get(i), alfabeto.get(j));

                for (int k = 0; k < listE.size(); k++) {
                    listObj obj = new listObj();
                    obj.setEst(getEstadosTransicion(listE.get(k), "epsilon"));
                    obj.insert(listE.get(k));
                    matDFA.get(i).add(obj);
                }

                if (listE.isEmpty()) {
                    listObj objVacio = new listObj();
                    matDFA.get(i).add(objVacio);
                }
            }

            ArrayList<ArrayList<String>> nuevos = buscaElementosNuevo(matAux, matDFA.get(i));

            for (int x = 0; x < nuevos.size(); x++) {
                matAux.add(nuevos.get(x));
                n++;
            }
        }
        /*
         System.out.println("Imprimiendo Matriz DFA");
         for (int p = 0; p < matDFA.size(); p++) {
         System.out.print("--");
         for (int g = 0; g < matDFA.get(p).size(); g++) {
         for (int h = 0; h < matDFA.get(p).get(g).getEst().size(); h++) {
         System.out.print(matDFA.get(p).get(g).getEst().get(h));
         }
         }
         System.out.println("--");
         }
         System.out.println();

         System.out.println("Imprimiendo Matriz aux");
         for (int i = 0; i < matAux.size(); i++) {
         System.out.println(matAux.get(i).toString());
         }*/

    }

    public ArrayList<ArrayList<String>> buscaElementosNuevo(ArrayList<ArrayList<String>> matAux, ArrayList<listObj> rowDFA) {

        ArrayList<ArrayList<String>> nuevos = new ArrayList<>();
        for (int i = 0; i < rowDFA.size(); i++) {
            String f = "";
            String a = "";
            for (int k = 0; k < rowDFA.get(i).getEst().size(); k++) {
                f = a.concat(rowDFA.get(i).getEst().get(k));

            }

            int j = 0;
            boolean band1 = true;
            while (band1 && (j < matAux.size())) {
                String h = "";
                String b = "";
                for (int x = 0; x < matAux.get(j).size(); x++) {
                    h = b.concat(matAux.get(j).get(x));
                }

                if (f.equals(h)) {
                    band1 = false;
                } else {
                    j++;
                }
            }

            if (band1 && (f != null) && (!f.equals(""))) {

                System.out.println(rowDFA.get(i).getEst().toString());
                
                
                
                nuevos.add(rowDFA.get(i).getEst());
            }
//            else {
//                System.out.println(f + "  ya exite en mat aux");
//            }
        }
        return nuevos;
    }

    private void limpialistas() {
        matDFA = null;
        matDFA = new ArrayList<>();
        matAux = null;
        matAux = new ArrayList<>();
    }

    public DFA construyeAutomataDFA() {
        limpialistas();
        matDFA();
        DFA myDFA = new DFA();
        myDFA.setEstados(cargaEstadosDFA());
        myDFA.setAlfabeto(alfabeto);
        myDFA.setInicial(inicial);
        myDFA.setFinales(cargaFinalesDFA());
        myDFA.setTransiciones(cargaTransicionesDFA());
        return myDFA;
    }

    private ArrayList<Estado> cargaEstadosDFA() {

        ArrayList<Estado> listEstados = new ArrayList<>();

        for (int i = 0; i < matAux.size(); i++) {
            String concat = "";
            for (int j = 0; j < matAux.get(i).size(); j++) {
                concat += matAux.get(i).get(j) + ",";
            }
            listEstados.add(new Estado(concat, false, false));
        }
        return listEstados;
    }

    private ArrayList<Estado> cargaFinalesDFA() {

        ArrayList<Estado> listFinalesDFA = new ArrayList<>();

        for (int i = 0; i < matAux.size(); i++) {
            for (int j = 0; j < matAux.get(i).size(); j++) {
                for (int f = 0; f < finales.size(); f++) {
                    if (matAux.get(i).get(j).equals(finales.get(f).getId())) {
                        listFinalesDFA.add(new Estado(matAux.get(i).get(j), false, true));
                    }
                }
            }
        }
        return listFinalesDFA;
    }

    private ArrayList<Transicion> cargaTransicionesDFA() {

        ArrayList<Transicion> listTransicionesDFA = new ArrayList<>();
        ArrayList<Estado> listEst = cargaEstadosDFA();
        ArrayList<ArrayList<String>> listTran = listOfList();

        for (int i = 0; i < listEst.size(); i++) {
            for (int j = 0; j < alfabeto.size(); j++) {
                listTransicionesDFA.add(new Transicion(listEst.get(i), alfabeto.get(j), new Estado(listTran.get(i).get(j), false, false)));
            }
        }
        return listTransicionesDFA;
    }

    private ArrayList<ArrayList<String>> listOfList() {
        ArrayList<ArrayList<String>> matriz = new ArrayList<>();

        for (int i = 0; i < matDFA.size(); i++) {
            ArrayList<String> listForRow = new ArrayList<>();
            for (int j = 0; j < matDFA.get(i).size(); j++) {

                String concat = "";
                for (int x = 0; x < matDFA.get(i).get(j).getEst().size(); x++) {
                    concat += matDFA.get(i).get(j).getEst().get(x) + ",";
                }
                listForRow.add(concat);
            }
            matriz.add(listForRow);
        }
        return matriz;
    }

    public boolean recognize(String s) {
        if (isDFA()) {
            return recognizeDFA(s);
        } else {
            return recognizeNFA(s);
        }
    }

    private boolean recognizeDFA(String s) {
        Estado actual = this.getInicial();
        for (int i = 0; i < s.length(); i++) {
            actual = buscarEstado(actual, String.valueOf(s.charAt(i)));
        }
        System.err.println("actual->" + actual.toString());
        return ((actual.ES_FINAL()) ? true : false);
    }

    private Estado buscarEstado(Estado actual, String subhilera) {
        Estado siguiente = null;
        for (Transicion t : this.getTransiciones()) {
            if (t.getOrigen().equals(actual) && t.getVariable().equals(subhilera)) {
                siguiente = t.getDestino();
            }
        }
        return siguiente;
    }

    private boolean recognizeNFA(String s) {
        ArrayList<Estado> actuales = new ArrayList<>();
        LinkedList<Estado> stack = new LinkedList<>();
        stack.add(this.inicial);
        int i = 0;
        int tamano = s.length();
        return backcrapeando(s, stack, actuales, i, tamano);
    }

    private boolean backcrapeando(String s, LinkedList<Estado> stack, ArrayList<Estado> actuales, int i, int tamano) {
        Estado es = null;
        if (tamano > i) {
            es = stack.pollFirst();
            if (es != null) {
                actuales = null;
                actuales = getEstadosTransicion(es, s.charAt(i) + "");
                if (actuales != null) {
                    for (int j = 0; j < actuales.size(); j++) {
                        stack.push(actuales.get(j));
                    }
                }
                backcrapeando(s, stack, actuales, ++i, tamano);
            }
        }
        actuales = null;
        actuales = new ArrayList<>();
        boolean bool = false;
        while (!stack.isEmpty()) {
            actuales.add(stack.pollFirst());
        }
        for (int j = 0; j < actuales.size(); j++) {
            if (actuales.get(j).ES_FINAL()) {
                System.out.println("s:" + j);
                bool = true;
                break;
            }
        }
        System.out.println("bool:" + bool);
        return bool;
    }

    public ArrayList<Estado> getEstadosTransicion(Estado actual, String alfabeto) {
        ArrayList<Estado> posibles = new ArrayList<>();
        for (Transicion tr : transiciones) {
            if (tr.getOrigen().getId().equals(actual.getId()) && tr.getVariable().equals(alfabeto)) {
                posibles.add(tr.getDestino());
            }
        }
        return posibles;
    }

    public ArrayList<String> getEstadosTransicion(ArrayList<String> actual, String alfabeto) {

        ArrayList<String> posibles = new ArrayList<>();
        for (Transicion tr : transiciones) {
            for (int i = 0; i < actual.size(); i++) {
                if (tr.getOrigen().getId().equals(actual.get(i)) && tr.getVariable().equals(alfabeto)) {
                    posibles.add(tr.getDestino().getId());
                }
            }
        }
        return posibles;
    }

    public ArrayList<String> getEstadosTransicion(String actual, String alfabeto) {

        ArrayList<String> posibles = new ArrayList<>();
        for (Transicion tr : transiciones) {
            if (tr.getOrigen().getId().equals(actual) && tr.getVariable().equals(alfabeto)) {
                posibles.add(tr.getDestino().getId());
            }

        }
        return posibles;
    }

    @Override
    public String toString() {
        return "Automaton{" + "estados=" + estados + ", \nalfabeto=" + alfabeto + ", \ntransiciones=" + transiciones + ", inicial=" + inicial + ", finales=" + finales + '}';
    }
}
